﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections;
using System.Threading;

namespace SerialManager
{
    public enum FileListBuilderStatus
    {
        InstanceCreated,
        Initialized,
        InitializationFailed,
        BuildingStarted,
        BuildingFinished,
        PreparingMovement,
        MovementPrepared
    }

    /// <summary>
    /// Class searching specified directory for serial files
    /// </summary>
    public sealed class FileListBuilder
    {
        /// <summary>
        /// Indicates current status of class
        /// </summary>
        public FileListBuilderStatus Status { get; private set; }

        /// <summary>
        /// Directory used for processing
        /// It's set in constructor
        /// </summary>
        public readonly DirectoryInfo Directory;

        /// <summary>
        /// Input list of files to process
        /// It's set in constructor
        /// </summary>
        public readonly FileInfo[] InputFileList;

        /// <summary>
        /// List of found files, that were recognized as part of serial
        /// Contains instance list of <see cref="EpisodeFiles"/> class
        /// It's filled with <see cref="BuildFileList"/> method
        /// </summary>
        public ArrayList FoundFiles { get; private set; }

        /// <summary>
        /// Files not supported for any kind of processing, they can just be moved to another directory
        /// It's filled with <see cref="BuildFileList"/> method
        /// </summary>
        public FileInfo[] UnknownFiles { get; private set; }

        /// <summary>
        /// Valid files by extension, but name doesn't contains information about season and episode
        /// User can supply needed information by hand
        /// It's filled with <see cref="BuildFileList"/> method
        /// </summary>
        public FileInfo[] NoMatchFiles { get; private set; }

        /// <summary>
        /// Informations about possible renaming operations
        /// It's filled with <see cref="PrepareMove"/> method
        /// </summary>
        public MoveResult FilesForMove { get; private set; }

        private readonly log4net.ILog Logger
            = log4net.LogManager.GetLogger( typeof( FileListBuilder ) );

        /// <summary>
        /// Constuction and initialization of class
        /// Sets <see cref="Status"/> property to <see cref="FileListBuilderStatus.Initialized"/> if successful
        /// or to <see cref="FileListBuilderStatus.InitializationFailed"/> in case of problem and calls <see cref="Core.Exit"/> too
        /// </summary>
        /// <param name="directory">Directory to process</param>
        public FileListBuilder( DirectoryInfo directory )
        {
            Status = FileListBuilderStatus.InstanceCreated;

            if( !directory.Exists )
            {
                Logger.FatalFormat( "Directory with serial ({0}) wasn't found", directory );
                Status = FileListBuilderStatus.InitializationFailed;
                Core.Exit( ExitCode.DirectoryNotFound );
                return;
            }

            Logger.InfoFormat( "Using directory: {0}", directory );
            Directory = directory;

            InputFileList = directory.GetFiles( "*.*", SearchOption.AllDirectories );
            if( InputFileList.Length == 0 )
            {
                Logger.FatalFormat( "Directory is empty", directory );
                Status = FileListBuilderStatus.InitializationFailed;
                Core.Exit( ExitCode.DirectoryIsEmpty );
                return;
            }

            Logger.InfoFormat( "Found files: {0}", InputFileList.Length );
            Console.WriteLine();

            Status = FileListBuilderStatus.Initialized;
        }

        private FileListBuilder() { }

        /// <summary>
        /// Builds list of files, which can be processed some way
        /// Sets <see cref="Status"/> property to <see cref="FileListBuilderStatus.BuildingFinished"/> on completition
        /// It's always successful, need to check properties <see cref="FoundFiles"/>, <see cref="UnknownFiles"/> and <see cref="NoMatchFiles"/> 
        /// for results that can be processed different ways
        /// </summary>
        public void BuildFilesList()
        {
            Status = FileListBuilderStatus.BuildingStarted;

            // basic lookup for valid files by their extension
            var extensions = EpisodeFiles.VideoExtensions.Concat( EpisodeFiles.SubtitleExtensions ).ToArray();
            var validFiles = (
                from file in InputFileList
                where file.HasAnyExtension( extensions )
                select file
            );

            Logger.DebugFormat( "Found {0} valid files", validFiles.Count() );

            // files with unknown extensions can be processed later
            UnknownFiles = (
                from file in InputFileList.Except( validFiles )
                where ( file.Attributes & FileAttributes.Archive ) != 0
                select file
            ).ToArray();

            if( UnknownFiles.Count() > 0 )
                Logger.DebugFormat( "Found {0} unknown files", UnknownFiles.Count() );

            FoundFiles = new ArrayList();
            var noMatch = new List<FileInfo>();

            // finally assemble files of each episode together
            foreach( var file in validFiles )
            {
                var info = EpisodeInfo.SearchForMatch( file );
                if( info == null )
                {
                    Logger.DebugFormat( "File {0} has no match", file );
                    noMatch.Add( file );
                    continue;
                }

                var index = FoundFiles.IndexOf( info );

                // first file for this episode
                if( index == -1 )
                {
                    var episodeFiles = new EpisodeFiles( info );
                    episodeFiles.Add( file );
                    FoundFiles.Add( episodeFiles );
                }
                else
                    ( (EpisodeFiles) FoundFiles[ index ] ).Add( file );
            }

            NoMatchFiles = noMatch.ToArray();
            Status = FileListBuilderStatus.BuildingFinished;
        }

        /// <summary>
        /// Priprava na prejmenovani souboru
        /// </summary>
        /// <param name="episodeTitles">Pole informaci o epizodach obsahujici nazvy epizod</param>
        public void PrepareMove( List<EpisodeInfo> episodeTitles )
        {
            if( Status <= FileListBuilderStatus.BuildingStarted )
                return;

            if( FoundFiles.Count == 0 )
            {
                Status = FileListBuilderStatus.MovementPrepared;
                return;
            }

            Status = FileListBuilderStatus.PreparingMovement;

            var forMove = new MoveResult();

            bool lookForTitle = ( episodeTitles != null && episodeTitles.Count > 0 );
            foreach( EpisodeFiles episode in FoundFiles )
            {
                if( lookForTitle )
                {
                    var index = episodeTitles.IndexOf( episode.Info );
                    if( index >= 0 )
                        episode.Info.Title = episodeTitles[ index ].Title;
                }
                episode.PrepareMove( ref forMove );
            }

            FilesForMove = forMove;
            Status = FileListBuilderStatus.MovementPrepared;
        }

        /// <summary>
        /// Provede konecne prejmenovani souboru
        /// </summary>
        public void ProcessMove()
        {
            foreach( var moveInfo in FilesForMove )
                moveInfo.Key.MoveTo( moveInfo.Value.FullName );
        }

        /// <summary>
        /// Trida pro evidenci informaci o chystanem prejmenovani souboru
        /// Klicem je originalni soubor a hodnotou nova absolutni cesta
        /// </summary>
        public class MoveResult : Dictionary<FileInfo, FileInfo>
        {
            public void Add( FileInfo original, string targetPath )
            {
                var newName = targetPath + original.Extension;
                if( newName == original.FullName )
                    return;
                base.Add( original, new FileInfo( newName ) );
            }
        }
    }
}
